package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

/**
 * @author Petrichor
 */
@Component
@Aspect
@Slf4j
public class GuiGuCacheAspect {

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    @Autowired
    private RedissonClient redissonClient ;

    @Around("@annotation(guiGuCache)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint , GuiGuCache guiGuCache) {

        // 从缓存中进行数据查询
        String redisKeyExpression = guiGuCache.cacheKey() ;   // album:info:#{#params[0]}
        String redisKey = parseExpression(proceedingJoinPoint, redisKeyExpression, String.class);
        String jsonData = redisTemplate.opsForValue().get(redisKey);

        // 获取目标方法的返回值类型  -----> 需要通过反射完成
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();            // 获取到了目标方法对象
        Type genericReturnType = method.getGenericReturnType(); // 获取目标方法的返回值类型

        if(!StringUtils.isEmpty(jsonData)) {
            // redis中存在专辑的基本数据
            log.info("从redis中查询到的专辑数据...");
            Object result = JSON.parseObject(jsonData, genericReturnType);
            return result ;
        }

        if(guiGuCache.enableLock() && !StringUtils.isEmpty(guiGuCache.lockName())) {        // 要使用分布式锁

            // 获取分布式锁

            String lockNameExpression = guiGuCache.lockName();
            String lockName = parseExpression(proceedingJoinPoint, lockNameExpression, String.class);
            RLock rLock = redissonClient.getLock(lockName);
            boolean tryLock = rLock.tryLock();
            if(tryLock) {

                try {

                    log.info("{}线程获取到了分布式锁, 从数据库中进行数据的查询..." , Thread.currentThread().getId());

                    // 从数据库中查询数据
                    Object result = proceedingJoinPoint.proceed();
                    if(result != null) {
                        log.info("从数据库中查询到了专辑的数据.." );
                        redisTemplate.opsForValue().set(redisKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);
                    }else {
                        redisTemplate.opsForValue().set(redisKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TEMPORARY_TIMEOUT , TimeUnit.SECONDS);
                    }

                    return result ;

                }catch (Throwable e) {
                    e.printStackTrace();
                    return null ;
                } finally {
                    log.info("{}线程释放了分布式锁..." , Thread.currentThread().getId());
                    rLock.unlock();
                }
            }else {
                log.info("{}线程没有获取到了分布式锁, 进行自旋..." , Thread.currentThread().getId());
                return around(proceedingJoinPoint , guiGuCache) ;
            }

        }else {

            try {

                // 从数据库中查询数据
                Object result = proceedingJoinPoint.proceed();
                if(result != null) {
                    log.info("从数据库中查询到了专辑的数据...");
                    redisTemplate.opsForValue().set(redisKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TIMEOUT , TimeUnit.SECONDS);
                }else {
                    redisTemplate.opsForValue().set(redisKey , JSON.toJSONString(result) , RedisConstant.ALBUM_TEMPORARY_TIMEOUT , TimeUnit.SECONDS);
                }

                return result ;

            }catch (Throwable e) {
                e.printStackTrace();
                return null ;
            }

        }


    }

    // 创建一个SpelExpressionParser表达式解析器对象
    public SpelExpressionParser spelExpressionParser = new SpelExpressionParser() ;

    public <T> T parseExpression(ProceedingJoinPoint proceedingJoinPoint , String expressionStr , Class<T> clazz) {

        // 调用SpelExpressionParser对象中的parseExpression方法解析表达式，得到表达式对象
        Expression expression = spelExpressionParser.parseExpression(expressionStr , ParserContext.TEMPLATE_EXPRESSION);

        // 创建一个StandardEvaluationContext对象，该对象就是用来封装预设数据
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext() ;
        standardEvaluationContext.setVariable("params" , proceedingJoinPoint.getArgs());

        // 从表达式对象中获取解析的结果
        T value = expression.getValue(standardEvaluationContext, clazz);

        // 返回
        return value ;

    }

}